<?php

/**
 * Description of x10_Util_URL
 *
 * @author prometheus
 * @property String $part_scheme A használatba veendő wrapper
 * @property String $part_userinfo felhasználó:jelszó
 * @property String $part_authority userinfo + ["@" + host] + [":" + port]
 * @property String $part_host Hosztnév, vagy IP cím (IPv6 támogatva!)
 * @property String $part_port Port szám
 * @property String $part_path Erőforrás elérési útja és neve
 * @property String $part_query Az erőforráshoz kapcsolódó lekérés ("?" után)
 * @property String $part_fragment Töredék azonosító ("#" után)
 */
class x10_Util_URL
{
	/**
	 * Az objektum által kezelt URL
	 * @var String
	 */
	protected $url = '';
	
	/**
	 * Az objektum által kezelt URL részletei:
	 * <ul>
	 * <li>scheme: A használatba veendő wrapper</li>
	 * <li>userinfo: &lt;felhasználó:jelszó&gt;</li>
	 * <li>authority: userinfo + ["@" + host] + [":" + port]</li>
	 * <li>host: Hosztnév, vagy IP cím (IPv6 támogatva!)</li>
	 * <li>port: Port szám</li>
	 * <li>path: Erőforrás elérési útja és neve</li>
	 * <li>query: Az erőforráshoz kapcsolódó lekérés ("?" után)</li>
	 * <li>fragment: Töredék azonosító ("#" után)</li>
	 * </ul>
	 * @var Array
	 */
	protected $exploded = array();

	/**
	 * Konstruktor.
	 * Helyes URL-t, vagy az {@link explode} kimeneteként létrejött tömböt vár.
	 * @param String|Array $url URL
	 */
	public function __construct($url)
	{
		$this->set($url);
	}

	/**
	 * Példányosít egy új URL objektumot.
	 * @param String|Array $url
	 * @return x10_Util_URL
	 */
	static public function create($url)
	{
		return new x10_Util_URL($url);
	}

	/**
	 * Megállapítja, hogy a kezelt URL rendelkezik-e érvényes sémával. Ha
	 * nincs séma, az érvénytelennek számít.
	 * @return Bool
	 */
	public function hasValidScheme()
	{
		return isset($this->exploded['part_scheme'])? in_array($this->exploded['part_scheme'], stream_get_wrappers()) : FALSE;
	}

	/**
	 * Megállapítja a kezelt URL érvényességét.
	 * @return Bool
	 */
	public function isValid()
	{
		$args = func_get_args();
		$url = (count($args)==1? $args[0] : $this->url);

		if (!isset($url)) return FALSE;
		if (empty($url)) return FALSE;
		if ($url == '/') return TRUE;

		$r  = "(?:([a-z0-9+-._]+)://)?";
		$r .= "(?:";
		$r .=	"(?:((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9a-f]{2})*)@)?";
		$r .=	"(?:\[((?:[a-z0-9:])*)\])?";
		$r .=	"((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9a-f]{2})*)?";
		$r .=	"(?::(\d*))?";
		$r .=	"(/(?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9a-f]{2})*)?";
		$r .=	"|";
		$r .=	"(/?";
		$r .=		"(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+";
		$r .=		"(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9a-f]{2})*";
		$r .=	")?";
		$r .= ")";
		$r .= "(?:\?((?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9a-f]{2})*))?";
		$r .= "(?:#((?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9a-f]{2})*))?";
		return (bool)preg_match('`'.$r.'`i', $url);
	}

	/**
	 * Beállítja az objektumhoz tartozó URL-t.
	 * Helyes URL-t, vagy az {@link explode} kimeneteként létrejött tömböt vár.
	 * @param String|Array $url URL
	 */
	public function set($url)
	{
		if (is_array($url))
		{
			$this->exploded = $url;
			$this->url = $this->implode();
		}
		else if (is_string($url) and $this->isValid($url))
		{
			$this->url = $url;
			$this->exploded = $this->explode();
		}
		else
		{
			$this->url = '';
			$this->exploded = array();
		}

		return $this;
	}

	/**
	 * Visszaadja az objektumhoz tartozó URL-t.
	 * @return String
	 */
	public function get()
	{
		return $this->url;
	}

	/**
	 * Szétbontja az URL-t.
	 * A visszakapott tömb elemei lehetnek:
	 * <ul>
	 * <li>scheme: A használatba veendő wrapper</li>
	 * <li>userinfo: &lt;felhasználó:jelszó&gt;</li>
	 * <li>authority: userinfo + ["@" + host] + [":" + port]</li>
	 * <li>host: Hosztnév, vagy IP cím (IPv6 támogatva!)</li>
	 * <li>port: Port szám</li>
	 * <li>path: Erőforrás elérési útja és neve</li>
	 * <li>query: Az erőforráshoz kapcsolódó lekérés ("?" után)</li>
	 * <li>fragment: Töredék azonosító ("#" után)</li>
	 * </ul>
	 * Csak azok a kulcsok jönnek létre, amelyek tartalmaznak értéket.
	 * @return Array
	 */
	public function explode()
	{
		if (!$this->isValid()) return FALSE;

		$r  = "(?:([a-z0-9+-._]+)://)?";
		$r .= "(?:";
		$r .=	"(?:((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9a-f]{2})*)@)?";
		$r .=	"(?:\[((?:[a-z0-9:])*)\])?";
		$r .=	"((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9a-f]{2})*)?";
		$r .=	"(?::(\d*))?";
		$r .=	"(/(?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9a-f]{2})*)?";
		$r .=	"|";
		$r .=	"(/?";
		$r .=		"(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+";
		$r .=		"(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9a-f]{2})*";
		$r .=	")?";
		$r .= ")";
		$r .= "(?:\?((?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9a-f]{2})*))?";
		$r .= "(?:#((?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9a-f]{2})*))?";
		preg_match('`'.$r.'`i', $this->url, $match);
		$parts = array
		(
			'url-scheme'	=> '',
			'url-userinfo'	=> '',
			'url-authority'	=> '',
			'url-host'		=> '',
			'url-port'		=> '',
			'url-path'		=> '',
			'url-query'		=> '',
			'url-fragment'	=> ''
		);
		switch (count($match))
		{
			case 10: $parts['part_fragment'] = $match[9];
			case 9: $parts['part_query'] = $match[8];
			case 8: $parts['part_path'] =  $match[7];
			case 7: $parts['part_path'] =  $match[6].(isset($parts['part_path'])? $parts['part_path'] : '');
			case 6: $parts['part_port'] =  $match[5];
			case 5: $parts['part_host'] =  $match[3]? "[".$match[3]."]" : $match[4];
			case 4: $parts['part_userinfo'] =  $match[2];
			case 3: $parts['part_scheme'] =  $match[1];
		}

		$parts['part_authority'] =
		(
			(
				$parts['part_userinfo']? $parts['part_userinfo']."@" : ""
			).
			(
				$parts['part_host']? $parts['part_host'] : ""
			).
			(
				$parts['part_port']? ":".$parts['part_port'] : ""
			)
		);

		$this->exploded = $parts;

		if (isset($this->exploded['part_query']))
		{
			parse_str($this->exploded['part_query'], $this->exploded['part_query']);
		}
		else
		{
			$this->exploded['part_query'] = array();
		}

		foreach ($this->exploded as $k=>$v) if (empty($v)) unset($this->exploded[$k]);
		
		return $this->exploded;
	}

	/**
	 * Összeépíti az URL-t a kapott részek és az URL részei alapján. A kapott
	 * részek felülírják az URL részeit.
	 * @param Array $with
	 * @return String
	 */
	public function implode($with=array())
	{
		$result = '';
		
		$result .= (isset($with['part_scheme'])? $with['part_scheme'] : (isset($this->exploded['part_scheme'])? $this->exploded['part_scheme'] : '')).'://';
		$result .= (isset($with['part_userinfo'])? $with['part_userinfo'] : (isset($this->exploded['part_userinfo'])? $this->exploded['part_userinfo'] : '')).'@';
		$result .= (isset($with['part_host'])? $with['part_host'] : (isset($this->exploded['part_host'])? $this->exploded['part_host'] : ''));
		$result .= (isset($with['part_port'])? ':'.$with['part_port'] : (isset($this->exploded['part_port'])? ':'.$this->exploded['part_port'] : ''));
		$result .= (isset($with['part_path'])? $with['part_path'] : (isset($this->exploded['part_path'])? $this->exploded['part_path'] : ''));
		$result .= (isset($with['part_query'])? '?'.(is_array($with['part_query'])? http_build_query($with['part_query']) : $with['part_query']) : (isset($this->exploded['part_query'])? '?'.http_build_query($this->exploded['part_query']) : ''));
		$result .= (isset($with['part_fragment'])? '#'.$with['part_fragment'] : (isset($this->exploded['part_fragment'])? '#'.$this->exploded['part_fragment'] : ''));

		return $result;
	}

	/**
	 * Visszatér az URL-en elért kiszolgáló által küldött HTTP fejlécekkel.
	 * @return Array
	 */
	public function getHeaders()
	{
		return get_headers($this->url);
	}

	/**
	 * Visszatér az URL-en elért HTML tartalomban található &lt;meta&gt;
	 * információkkal.
	 * @return Array
	 */
	public function getMetaTags()
	{
		return get_meta_tags($this->url);
	}

	/**
	 * Az URL lekéréséhez tartozó paramétereit összeépíti a kapott
	 * paraméterekkel.
	 * @param Array $data
	 */
	public function buildQueryMerge($data)
	{
		$this->exploded['part_query'] = array_merge_recursive($this->exploded['part_query'], $data);
		$this->url = $this->implode();
	}

	public function __isset($key)
	{
		return isset($this->exploded[$key]);
	}

	public function __get($key)
	{
		return isset($this->exploded[$key])? $this->exploded[$key] : NULL;
	}

	public function __set($key, $value)
	{
		$this->exploded[$key] = $value;
		$this->url = $this->implode();
	}

	public function __unset($key)
	{
		if (isset($this->exploded[$key])) unset($this->exploded[$key]);
	}

	public function __clone()
	{
		return new x10_Util_URL($this->url);
	}

	public function __toString()
	{
		return $this->get();
	}
}

?>